From dd0ce3287ffddf3fd0dd5d7af1ebda60bd871c85 Mon Sep 17 00:00:00 2001 From: LogicParrot Date: Sun, 27 Mar 2016 20:43:30 +0300 Subject: Players never fall through unloaded chunks or end up inside solids on teleport --- src/Chunk.cpp | 2 +- src/ClientHandle.cpp | 47 ++++++++++++++++++++- src/ClientHandle.h | 9 ++++ src/Entities/Player.cpp | 107 ++++++++++++++++++++++++++++++++++++------------ src/Entities/Player.h | 2 + 5 files changed, 138 insertions(+), 29 deletions(-) diff --git a/src/Chunk.cpp b/src/Chunk.cpp index db8966a23..c1baae9b2 100644 --- a/src/Chunk.cpp +++ b/src/Chunk.cpp @@ -638,13 +638,13 @@ void cChunk::Tick(std::chrono::milliseconds a_Dt) } // Do not move mobs that are detached from the world to neighbors. They're either scheduled for teleportation or for removal. + // Because the schedulded destruction is going to look for them in this chunk. See cEntity::destroy. if (!(*itr)->IsTicking()) { ++itr; continue; } - // Because the schedulded destruction is going to look for them in this chunk. See cEntity::destroy. if ((((*itr)->GetChunkX() != m_PosX) || ((*itr)->GetChunkZ() != m_PosZ)) ) diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index 6fb2033f4..036b0250d 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -3,6 +3,7 @@ #include "ClientHandle.h" #include "Server.h" #include "World.h" +#include "Chunk.h" #include "Entities/Pickup.h" #include "Bindings/PluginManager.h" #include "Entities/Player.h" @@ -65,6 +66,7 @@ cClientHandle::cClientHandle(const AString & a_IPString, int a_ViewDistance) : m_RequestedViewDistance(a_ViewDistance), m_IPString(a_IPString), m_Player(nullptr), + m_CachedSentChunk(0, 0), m_HasSentDC(false), m_LastStreamedChunkX(0x7fffffff), // bogus chunk coords to force streaming upon login m_LastStreamedChunkZ(0x7fffffff), @@ -336,6 +338,7 @@ void cClientHandle::Authenticate(const AString & a_Name, const AString & a_UUID, // Spawn player (only serversided, so data is loaded) m_Player = new cPlayer(m_Self, GetUsername()); + InvalidateCachedSentChunk(); m_Self.reset(); cWorld * World = cRoot::Get()->GetWorld(m_Player->GetLoadedWorldName()); @@ -1869,6 +1872,18 @@ void cClientHandle::RemoveFromWorld(void) +void cClientHandle::InvalidateCachedSentChunk() +{ + ASSERT(m_Player != nullptr); + // Sets this to a junk value different from the player's current chunk, which invalidates it and + // ensures its value will not be used. + m_CachedSentChunk = cChunkCoords(m_Player->GetChunkX() + 500, m_Player->GetChunkZ()); +} + + + + + bool cClientHandle::CheckBlockInteractionsRate(void) { ASSERT(m_Player != nullptr); @@ -1926,6 +1941,36 @@ void cClientHandle::Tick(float a_Dt) return; } + + // Freeze the player if it is standing on a chunk not yet sent to the client + { + bool PlayerIsStandingAtASentChunk = false; + // If the chunk is invalid, do not bother checking if it's sent to the client, it is definitely not + if (m_Player->GetParentChunk()->IsValid()) + { + // Before iterating m_SentChunks, see if the player's coords equal m_CachedSentChunk + // If so, the chunk has been sent to the client. This is an optimization that saves an iteration of m_SentChunks. + if (cChunkCoords(m_Player->GetChunkX(), m_Player->GetChunkZ()) == m_CachedSentChunk) + { + PlayerIsStandingAtASentChunk = true; + } + else + { + // This block is entered only when the player moves to a new chunk, invalidating the cached coords. + // Otherwise the cached coords are used. + cCSLock Lock(m_CSChunkLists); + auto itr = std::find(m_SentChunks.begin(), m_SentChunks.end(), cChunkCoords(m_Player->GetChunkX(), m_Player->GetChunkZ())); + if (itr != m_SentChunks.end()) + { + m_CachedSentChunk = *itr; + PlayerIsStandingAtASentChunk = true; + } + } + } + // The player will freeze itself if it is standing on a chunk not yet sent to the client + m_Player->TickFreezeCode(PlayerIsStandingAtASentChunk); + } + // If the chunk the player's in was just sent, spawn the player: if (m_HasSentPlayerChunk && (m_State == csDownloadingWorld)) { @@ -1957,7 +2002,7 @@ void cClientHandle::Tick(float a_Dt) } } - // Unload all chunks that are out of the view distance (all 5 seconds) + // Unload all chunks that are out of the view distance (every 5 seconds) if ((m_Player->GetWorld()->GetWorldAge() % 100) == 0) { UnloadOutOfRangeChunks(); diff --git a/src/ClientHandle.h b/src/ClientHandle.h index 899b0a5ab..123d1b057 100644 --- a/src/ClientHandle.h +++ b/src/ClientHandle.h @@ -363,6 +363,8 @@ public: // tolua_export /** Returns the protocol version number of the protocol that the client is talking. Returns zero if the protocol version is not (yet) known. */ UInt32 GetProtocolVersion(void) const { return m_ProtocolVersion; } // tolua_export + void InvalidateCachedSentChunk(); + private: friend class cServer; // Needs access to SetSelf() @@ -408,6 +410,13 @@ private: cPlayer * m_Player; + /** This is an optimization which saves you an iteration of m_SentChunks if you just want to know + whether or not the player is standing at a sent chunk. + If this is equal to the coordinates of the chunk the player is currrently standing at, then this must be a sent chunk + and a member of m_SentChunks. + Otherwise, this contains an arbitrary value which should not be used. */ + cChunkCoords m_CachedSentChunk; + bool m_HasSentDC; ///< True if a Disconnect packet has been sent in either direction // Chunk position when the last StreamChunks() was called; used to avoid re-streaming while in the same chunk diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index 804a92284..f4e7eee44 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -251,36 +251,11 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) // Handle a frozen player if (m_IsFrozen) { - m_FreezeCounter += 1; - if (!m_IsManuallyFrozen && a_Chunk.IsValid()) - { - // If the player was automatically frozen, unfreeze if the chunk the player is inside is loaded - Unfreeze(); - } - else - { - // If the player was externally / manually frozen (plugin, etc.) or if the chunk isn't loaded yet: - // 1. Set the location to m_FrozenPosition every tick. - // 2. Zero out the speed every tick. - // 3. Send location updates every 60 ticks. - SetPosition(m_FrozenPosition); - SetSpeed(0, 0, 0); - if (m_FreezeCounter % 60 == 0) - { - BroadcastMovementUpdate(m_ClientHandle.get()); - m_ClientHandle->SendPlayerPosition(); - } - return; - } - } - - if (!a_Chunk.IsValid()) - { - FreezeInternal(GetPosition(), false); - // This may happen if the cPlayer is created before the chunks have the chance of being loaded / generated (#83) return; } + ASSERT(a_Chunk.IsValid()); + super::Tick(a_Dt, a_Chunk); // Handle charging the bow: @@ -339,6 +314,79 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) +void cPlayer::TickFreezeCode(bool a_MyChunkIsSent) +{ + // This function is ticked by the player's client handle. This ensures it always ticks, even if the player + // is standing in an unloaded chunk, unlike cPlayer::Tick. We need this because the freeze handling code must + // also tick in unloaded chunks. + if (m_IsFrozen) + { + m_FreezeCounter += 1; + if ((!m_IsManuallyFrozen) && (a_MyChunkIsSent)) + { + cWorld::cLock Lock(*GetWorld()); + // If the player was automatically frozen, unfreeze if the chunk the player is inside is loaded + Unfreeze(); + + // Pull the player out of any solids that might have loaded on them. + PREPARE_REL_AND_CHUNK(GetPosition(), *(GetParentChunk())); + if (RelSuccess) + { + int NewY = Rel.y; + if (NewY < 0) + { + NewY = 0; + } + while (NewY < cChunkDef::Height - 2) + { + // If we find a position with enough space for the player + if ( + (Chunk->GetBlock(Rel.x, NewY, Rel.z) == E_BLOCK_AIR) && + (Chunk->GetBlock(Rel.x, NewY + 1, Rel.z) == E_BLOCK_AIR) + ) + { + // If the found position is not the same as the original + if (NewY != Rel.y) + { + SetPosition(GetPosition().x, NewY, GetPosition().z); + GetClientHandle()->SendPlayerPosition(); + } + break; + } + ++NewY; + } + } + } + else + { + // If the player was externally / manually frozen (plugin, etc.) or if the chunk isn't loaded yet: + // 1. Set the location to m_FrozenPosition every tick. + // 2. Zero out the speed every tick. + // 3. Send location updates every 60 ticks. + + if ((m_FreezeCounter % 60 == 0) || ((m_FrozenPosition - GetPosition()).SqrLength() > 2 * 2)) + { + SetPosition(m_FrozenPosition); + SetSpeed(0, 0, 0); + BroadcastMovementUpdate(m_ClientHandle.get()); + m_ClientHandle->SendPlayerPosition(); + } + return; + } + } + else + { + if (!a_MyChunkIsSent) + { + FreezeInternal(GetPosition(), false); + } + } +} + + + + + int cPlayer::CalcLevelFromXp(int a_XpTotal) { // level 0 to 15 @@ -1392,6 +1440,7 @@ void cPlayer::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ) if (!cRoot::Get()->GetPluginManager()->CallHookEntityTeleport(*this, m_LastPosition, Vector3d(a_PosX, a_PosY, a_PosZ))) { SetPosition(a_PosX, a_PosY, a_PosZ); + FreezeInternal(GetPosition(), false); m_LastGroundHeight = static_cast(a_PosY); m_bIsTeleporting = true; @@ -1746,6 +1795,9 @@ bool cPlayer::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d return false; } + // The clienthandle caches the coords of the chunk we're standing at. Invalidate this. + GetClientHandle()->InvalidateCachedSentChunk(); + // Prevent further ticking in this world SetIsTicking(false); @@ -1757,6 +1809,7 @@ bool cPlayer::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d // Set position to the new position SetPosition(a_NewPosition); + FreezeInternal(a_NewPosition, false); // Stop all mobs from targeting this player StopEveryoneFromTargetingMe(); diff --git a/src/Entities/Player.h b/src/Entities/Player.h index fae0e6177..777a533c9 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -50,6 +50,8 @@ public: virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; + void TickFreezeCode(bool a_MyChunkIsSent); + virtual void HandlePhysics(std::chrono::milliseconds a_Dt, cChunk &) override { UNUSED(a_Dt); } /** Returns the currently equipped weapon; empty item if none */ -- cgit v1.2.3